﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace KINECTMesh
{
    using Microsoft.Research.Kinect;
    using Microsoft.Research.Kinect.Nui;

    using KINECTKey;
    using KINECTUtility;
    using BombPart;
    

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private double windowWidth;
        private double windowHeight;

        private AreaChecker myAreaChecker;
        private RuleChecker myRuleChecker;

        public MainWindow()
        {
            InitializeComponent();

            int unit = 192;
            int v = 0;
            int winh = 768;
            int winw = 1280;
            windowHeight = winh;
            windowWidth = winw;

            myAreaChecker = new AreaChecker(windowWidth, windowHeight, unit, unit);

            int margin = 5;
            AreaDefinition areaDef = null;
            for (int vindex = 0; vindex < myAreaChecker.MaxVerticalIndex; vindex++)
            {
                for (int hindex = 0; hindex < myAreaChecker.MaxHorizontalIndex; hindex++)
                {
                    areaDef = myAreaChecker.GetArea(vindex, hindex);
                    var rect = new Rectangle();
                    double width = areaDef.Width - 2 * margin;
                    double height = areaDef.Height - 2 * margin;
                    rect.Width = width;
                    rect.Height = height;
                    rect.Fill = new SolidColorBrush(Colors.Azure);
                    rect.StrokeThickness = 1;
                    rect.Stroke = new SolidColorBrush(Colors.OrangeRed);
                    rect.Opacity = 0.5;
                    RootCanvas.Children.Add(rect);
                    Canvas.SetLeft(rect, areaDef.Left + margin);
                    Canvas.SetTop(rect, areaDef.Top + margin);
                    areaDef.Target = rect;
                }
            }

            myAreaChecker.RegistCheckJointID(JointID.HandLeft);
            myAreaChecker.RegistCheckJointID(JointID.HandRight);

            myNui = new Microsoft.Research.Kinect.Nui.Runtime();
            myNui.Initialize(Microsoft.Research.Kinect.Nui.RuntimeOptions.UseDepthAndPlayerIndex | Microsoft.Research.Kinect.Nui.RuntimeOptions.UseSkeletalTracking);
      //      myNui.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color);
            myNui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);
            
            lastTime = DateTime.Now;

            myNui.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>(myNui_DepthFrameReady);
            myNui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(myNui_SkeletonFrameReady);
   //         myNui.VideoFrameReady += new EventHandler<ImageFrameReadyEventArgs>(myNui_VideoFrameReady);

            myAreaChecker.AreaMoved += new AreaChecker.AreaCheckerUpdateEventHandler(myAreaChecker_AreaMoved);

            myAreaChecker.HoldTime = TimeSpan.FromSeconds(3);
            myAreaChecker.AreaHold +=new AreaChecker.AreaCheckerHoldEventHandler(myAreaChecker_AreaHold);
            myAreaChecker.AreaUnhold += new AreaChecker.AreaCheckerHoldEventHandler(myAreaChecker_AreaUnhold);

            myRuleChecker = new RuleChecker( myAreaChecker);
            myRuleChecker.AddRule(2, 2, RuleDefinition.ActionMode.Occupied);
            myRuleChecker.AddRule(3, 2, RuleDefinition.ActionMode.Occupied);
            myRuleChecker.AddRule(4, 2, RuleDefinition.ActionMode.Occupied);
            myRuleChecker.AddRule(4, 1, RuleDefinition.ActionMode.Occupied);
            myRuleChecker.AddRule(5, 1, RuleDefinition.ActionMode.Occupied);
            myRuleChecker.RuleCompleted += new RuleChecker.AreaRuleCheckerPerfectEventHandler(myRuleChecker_RuleCompleted);
            myRuleChecker.RuleProgressed += new RuleChecker.AreaRuleCheckerProgressEventHandler(myRuleChecker_RuleProgressed);
       //     myRuleChecker.StartTracing(JointID.HandLeft);
        }

        List<Shape> progressMarks = new List<Shape>();
        void myRuleChecker_RuleProgressed(RuleChecker source, int currentStep)
        {
            var progressRectangle = new Rectangle();
            progressMarks.Add(progressRectangle);
            progressRectangle.Height = 50;
            progressRectangle.Width = 30;
            progressRectangle.Stroke = new SolidColorBrush(Colors.Aquamarine);
            progressRectangle.StrokeThickness = 3;
            Canvas.SetTop(progressRectangle, 100);
            Canvas.SetLeft(progressRectangle, 30 + (progressRectangle.Width + 5) * currentStep);
            MarkingCanvas.Children.Add(progressRectangle);
        }

        void myRuleChecker_RuleCompleted(RuleChecker source)
        {
            var okCircle = new Ellipse();
            okCircle.Width = 300;
            okCircle.Height = 300;
            okCircle.Stroke = new SolidColorBrush(Colors.Crimson);
            Canvas.SetLeft(okCircle, 100);
            Canvas.SetTop(okCircle, 100);
            if (source.IsCompleted)
            {
                MarkingCanvas.Children.Add(okCircle);
                progressMarks.Add(okCircle);
            }
            else
            {
                foreach (var rect in progressMarks)
                {
                    MarkingCanvas.Children.Remove(rect);
                }
                progressMarks.Clear();
            }
        }

        void myAreaChecker_AreaUnhold(AreaCheckerHoldEventArgs e)
        {
            var area = myAreaChecker.GetArea(e.HoldIndexVertical, e.HoldIndexHorizontal);
            area.Target.StrokeThickness = 1;

            if (e.HoldJoint == JointID.HandLeft)
            {
                tbBLeftP.Text = String.Format("({0},{1})", e.HoldIndexHorizontal, e.HoldIndexVertical);
            }
            if (e.HoldJoint == JointID.HandRight)
            {
                tbBRightP.Text = String.Format("({0},{1})", e.HoldIndexHorizontal, e.HoldIndexVertical);
            }
        }

        void myAreaChecker_AreaHold(AreaCheckerHoldEventArgs e)
        {
            var area = myAreaChecker.GetArea(e.HoldIndexVertical, e.HoldIndexHorizontal);
            area.Target.StrokeThickness = 5;

            if (e.HoldJoint == JointID.HandLeft)
            {
                tbBLeftC.Text = String.Format("({0},{1})", e.HoldIndexHorizontal, e.HoldIndexVertical);
            }
            if (e.HoldJoint == JointID.HandRight)
            {
                tbBRightC.Text = String.Format("({0},{1})", e.HoldIndexHorizontal, e.HoldIndexVertical);
            }
        }

        void myAreaChecker_AreaMoved(AreaCheckerUpdateEventArgs e)
        {
            if (e.MovedJoint == JointID.HandLeft)
            {
                string direction = BuildMovingStatus(e);
                tbLeftHandMoving.Text = direction;
                MarkingCanvas.Children.Remove(myLeftHandMovingLine);
                DrawMovingLine(e, Colors.Red);

                if (System.Math.Abs(e.MoveHorizontalCells) > 2 || System.Math.Abs(e.MoveVerticalCells) > 2)
                {
                    AddBombAnimation(e);
                }
                tbBLeftC.Text = "";
            }
            if (e.MovedJoint == JointID.HandRight)
            {
                string direction = BuildMovingStatus(e);
                tbRightHandMoving.Text = direction;

                MarkingCanvas.Children.Remove(myRightHandMovingLine);
                DrawMovingLine(e, Colors.Blue);
                tbBRightC.Text = "";
            }
        }

        private void AddBombAnimation(AreaCheckerUpdateEventArgs e)
        {
            var area = myAreaChecker.GetArea(e.CurrentIndexVertical, e.CurrentIndexHorizontal);
            var bomb = new Bomb();
            double top = area.Top + (area.Height - bomb.BombHeight) / 2;
            double left = area.Left + (area.Width - bomb.BombWidth) / 2;

            Canvas.SetTop(bomb, top);
            Canvas.SetLeft(bomb, left);
            MarkingCanvas.Children.Add(bomb);

            bomb.Completed += new BombedDelegator(bomb_Completed);
            bomb.Fire();
        }

        void bomb_Completed(Bomb bomb)
        {
            MarkingCanvas.Children.Remove(bomb);
        }


        private Line myLeftHandMovingLine;
        private Line myRightHandMovingLine;

        private void DrawMovingLine(AreaCheckerUpdateEventArgs e, Color lineColor)
        {
            int prevHorizontalIndex = e.CurrentIndexHorizontal - e.MoveHorizontalCells;
            int prevVerticalIndex = e.CurrentIndexVertical - e.MoveVerticalCells;

            var startArea = myAreaChecker.GetArea(prevVerticalIndex, prevHorizontalIndex);
            if (startArea != null)
            {
                var line = new Line();
                line.X1 = 0;
                line.Y1 = 0;
                line.X2 = startArea.Width * e.MoveHorizontalCells;
                line.Y2 = startArea.Height * e.MoveVerticalCells;

                line.StrokeThickness = 20;
                line.Stroke = new SolidColorBrush(lineColor);
                line.StrokeStartLineCap = PenLineCap.Round;
                line.StrokeEndLineCap = PenLineCap.Triangle;
                MarkingCanvas.Children.Add(line);
                Canvas.SetLeft(line, startArea.IndexHorizontal * startArea.Width + startArea.Width / 2);
                Canvas.SetTop(line, startArea.Height * startArea.IndexVertical + startArea.Height / 2);
                if (e.MovedJoint == JointID.HandLeft)
                {
                    myLeftHandMovingLine = line;
                }
                else if (e.MovedJoint == JointID.HandRight)
                {
                    myRightHandMovingLine = line;
                }
            }
        }

        private static string BuildMovingStatus(AreaCheckerUpdateEventArgs e)
        {
            string direction = "";
            if (e.MoveToTop)
            {
                direction += "T";
            }
            if (e.MoveToBottom)
            {
                direction += "B";
            }
            if (e.MoveToLeft)
            {
                direction += "L";
            }
            if (e.MoveToRight)
            {
                direction += "R";
            }
            direction += String.Format(":H={0},V={1}", e.MoveHorizontalCells, e.MoveVerticalCells);
            return direction;
        }

        void myNui_VideoFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            throw new NotImplementedException();
        }

        void myNui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            SkeletonFrame frame = e.SkeletonFrame;
            float leftHandX = 0;
            float leftHandY = 0;
            float leftHandZ = 0;
            bool leftHandAvailable = false;
            float rightHandX = 0;
            float rightHandY = 0;
            float rightHandZ = 0;
            bool rightHandAvailable = false;

            foreach (var data in frame.Skeletons)
            {
                if (SkeletonTrackingState.Tracked == data.TrackingState)
                {
                    float w = data.Joints[JointID.HandLeft].Position.W;
                    float x = data.Joints[JointID.HandLeft].Position.X;
                    float y = data.Joints[JointID.HandLeft].Position.Y;
                    float z = data.Joints[JointID.HandLeft].Position.Z;
                    tbLeftHand.Text = String.Format("w={0:0.00},x={1:0.00},y={2:0.00},z={3:0.00}", w, x, y, z);
                    leftHandX = x;
                    leftHandY = y;
                    leftHandZ = z;
                    leftHandAvailable = true;

                    w = data.Joints[JointID.WristLeft].Position.W;
                    x = data.Joints[JointID.WristLeft].Position.X;
                    y = data.Joints[JointID.WristLeft].Position.Y;
                    z = data.Joints[JointID.WristLeft].Position.Z;
                    tbLeftTekubi.Text = String.Format("w={0:0.00},x={1:0.00},y={2:0.00},z={3:0.00}", w, x, y, z);

                    w = data.Joints[JointID.ElbowLeft].Position.W;
                    x = data.Joints[JointID.ElbowLeft].Position.X;
                    y = data.Joints[JointID.ElbowLeft].Position.Y;
                    z = data.Joints[JointID.ElbowLeft].Position.Z;
                    tbLeftElbow.Text = String.Format("w={0:0.00},x={1:0.00},y={2:0.00},z={3:0.00}", w, x, y, z);

                    w = data.Joints[JointID.HandRight].Position.W;
                    x = data.Joints[JointID.HandRight].Position.X;
                    y = data.Joints[JointID.HandRight].Position.Y;
                    z = data.Joints[JointID.HandRight].Position.Z;
                    tbRightHand.Text = String.Format("w={0:0.00},x={1:0.00},y={2:0.00},z={3:0.00}", w, x, y, z);
                    rightHandX = x;
                    rightHandY = y;
                    rightHandZ = z;
                    rightHandAvailable = true;

                    w = data.Joints[JointID.WristRight].Position.W;
                    x = data.Joints[JointID.WristRight].Position.X;
                    y = data.Joints[JointID.WristRight].Position.Y;
                    z = data.Joints[JointID.WristRight].Position.Z;
                    tbRightTekubi.Text = String.Format("w={0:0.00},x={1:0.00},y={2:0.00},z={3:0.00}", w, x, y, z);

                    w = data.Joints[JointID.ElbowLeft].Position.W;
                    x = data.Joints[JointID.ElbowLeft].Position.X;
                    y = data.Joints[JointID.ElbowLeft].Position.Y;
                    z = data.Joints[JointID.ElbowLeft].Position.Z;
                    tbRightElbo.Text = String.Format("w={0:0.00},x={1:0.00},y={2:0.00},z={3:0.00}", w, x, y, z);

                    // Current version is for only one person!!
                    break;
                }
            }

            myAreaChecker.BeginCheck();
            foreach (var ad in myAreaChecker.GetDefAreas())
            {
                ad.Target.Fill = new SolidColorBrush(Colors.Azure);
            }
            if (leftHandAvailable || rightHandAvailable)
            {
                if (leftHandAvailable)
                {
                    AreaDefinition areaDef = myAreaChecker.IsIncludePoint(leftHandX, leftHandY, JointID.HandLeft);
                    if (areaDef != null)
                    {
                        areaDef.Target.Fill = new SolidColorBrush(Colors.Red);
                    }
                }
                if (rightHandAvailable)
                {
                    AreaDefinition areaDef = myAreaChecker.IsIncludePoint(rightHandX, rightHandY, JointID.HandRight);
                    if (areaDef!=null)
                    {
                        areaDef.Target.Fill = new SolidColorBrush(Colors.Blue);
                    }
                }
            }
            myAreaChecker.EndCheck();
        }

        int totalFrames = 0;
        int lastFrames = 0;

        void myNui_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            PlanarImage image = e.ImageFrame.Image;
            byte[] convertedDepthFrame = convertDepthFrame(image.Bits);
            double dpiX = 96;
            double dpiY = 96;
            imageDepthStream.Source = BitmapSource.Create(
                image.Width, image.Height, dpiX, dpiY,
                PixelFormats.Bgr32, null, convertedDepthFrame, image.Width * 4);
            DateTime cur = DateTime.Now;
            if (cur.Subtract(lastTime) > TimeSpan.FromSeconds(1))
            {
                int frameDiff = totalFrames - lastFrames;
                lastFrames = totalFrames;
                lastTime = cur;
                tbFrameRate.Text = frameDiff.ToString() + " fps";
            }
        }

        byte[] depthFrame32 = new byte[320 * 240 * 4];
        const int RED_IDX = 0;
        const int GREEN_IDX = 1;
        const int BLUE_IDX = 2;

        // Converts a 16-bit grayscale depth frame which includes player indexes into a 32-bit frame
        // that displays different players in different colors
        byte[] convertDepthFrame(byte[] depthFrame16)
        {
            for (int i16 = 0, i32 = 0; i16 < depthFrame16.Length && i32 < depthFrame32.Length; i16 += 2, i32 += 4)
            {
                int player = depthFrame16[i16] & 0x07;
                int realDepth = (depthFrame16[i16 + 1] << 5) | (depthFrame16[i16] >> 3);
                // transform 13-bit depth information into an 8-bit intensity appropriate
                // for display (we disregard information in most significant bit)
                byte intensity = (byte)(255 - (255 * realDepth / 0x0fff));

                depthFrame32[i32 + RED_IDX] = 0;
                depthFrame32[i32 + GREEN_IDX] = 0;
                depthFrame32[i32 + BLUE_IDX] = 0;

                // choose different display colors based on player
                switch (player)
                {
                    case 0:
                        depthFrame32[i32 + RED_IDX] = (byte)(intensity / 2);
                        depthFrame32[i32 + GREEN_IDX] = (byte)(intensity / 2);
                        depthFrame32[i32 + BLUE_IDX] = (byte)(intensity / 2);
                        break;
                    case 1:
                        depthFrame32[i32 + RED_IDX] = intensity;
                        break;
                    case 2:
                        depthFrame32[i32 + BLUE_IDX] = intensity;
                        break;
                    case 3:
                        depthFrame32[i32 + GREEN_IDX] = intensity;
                        break;
                    case 4:
                        depthFrame32[i32 + RED_IDX] = intensity;
                        depthFrame32[i32 + BLUE_IDX] = intensity;
                        break;
                    case 5:
                        depthFrame32[i32 + RED_IDX] = intensity;
                        depthFrame32[i32 + GREEN_IDX] = intensity;
                        break;
                    case 6:
                        depthFrame32[i32 + GREEN_IDX] = intensity;
                        depthFrame32[i32 + BLUE_IDX] = intensity;
                        break;
                    case 7:
                        depthFrame32[i32 + RED_IDX] = (byte)(intensity / 2);
                        depthFrame32[i32 + GREEN_IDX] = intensity;
                        break;
                }
            }
            return depthFrame32;
        }

        private Microsoft.Research.Kinect.Nui.Runtime myNui;
        private DateTime lastTime;

        private void cbRule_Checked(object sender, RoutedEventArgs e)
        {
            if (cbRule.IsChecked.Value)
            {
                progressMarks.Clear();
                myRuleChecker.StartTracing(JointID.HandLeft);
            }
        }
    }
}
